package com.oceantrader.root.tools;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import lombok.AllArgsConstructor;
import lombok.Data;

public class SqlLoader {
    /** groovy script 缓存Map - 快速运行脚本的关键 */
    private static final Map<String, Class<?>> classCache = new ConcurrentHashMap<>();
    /** 定时器 - 定时清理脚本缓存 */
    private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    static {
        scheduler.scheduleAtFixedRate(SqlLoader::clearCache, 1, 1, TimeUnit.DAYS);
    }

    /** 清理脚本缓存，定时执行 */
    private static void clearCache() {
        classCache.clear();
    }

    /**
     * 执行Groovy脚本
     * 
     * @param scriptCode 脚本
     * @param parameters 脚本入参
     * @return 脚本返回值
     */
    public static Object executeGroovyScript(String scriptCode, Map<String, Object> parameters) {
        try {
            Class<?> groovyClass = classCache.computeIfAbsent(scriptCode, code -> {
                GroovyClassLoader classLoader = null;
                try {
                    classLoader = new GroovyClassLoader();
                    return classLoader.parseClass(code);
                } finally {
                    try {
                        if (classLoader != null) {
                            classLoader.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
            GroovyObject groovyObject = (GroovyObject) groovyClass.getDeclaredConstructor().newInstance();
            for (Map.Entry<String, Object> entry : parameters.entrySet()) {
                groovyObject.setProperty(entry.getKey(), entry.getValue());
            }
            return groovyObject.invokeMethod("run", null);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将XSQL转换为Groovy代码
     * 
     * @param sql XSQL脚本
     * @return Groovy代码
     */
    private static String sqlToGroovyCode(String sql) {
        int itemIndex = 0;
        String code = "String __sql__ = \"\";\n";
        code += "int __pindex__ = 0;\n";
        code += "boolean flag = false;\n";
        code += "Map<String, Object> __map__ = new HashMap<>();\n";
        String[] sqlSplits = sql.split("\n");
        for (String sqlSplit : sqlSplits) {
            sqlSplit = sqlSplit.trim();
            boolean isJava = false;
            if (sqlSplit.startsWith("if") && sqlSplit.endsWith("{")) {
                isJava = true;
            } else if (sqlSplit.startsWith("}")) {
                isJava = true;
            } else if (sqlSplit.startsWith("for") && sqlSplit.endsWith("{")) {
                isJava = true;
            }
            if (isJava) {
                code += sqlSplit + "\n";
                continue;
            }
            for (;;) {
                {
                    int opTag = sqlSplit.indexOf("[$");
                    if (opTag >= 0) {
                        code += "__sql__+=\"" + sqlSplit.substring(0, opTag) + "\";";
                        sqlSplit = sqlSplit.substring(opTag + 2);
                        int edTag = sqlSplit.indexOf("]");
                        code += "__sql__+=" + sqlSplit.substring(0, edTag) + ";";
                        sqlSplit = sqlSplit.substring(edTag + 1);
                        continue;
                    }
                }
                {
                    int opTag = sqlSplit.indexOf("[#");
                    if (opTag >= 0) {
                        code += "__sql__+=\"" + sqlSplit.substring(0, opTag) + "\";";
                        sqlSplit = sqlSplit.substring(opTag + 2);
                        int edTag = sqlSplit.indexOf("]");
                        code += "__sql__+=\"#{P_\"+__pindex__+\"}\";";
                        code += "__map__.put(\"P_\" + __pindex__," + sqlSplit.substring(0, edTag) + ");";
                        code += "__pindex__++;";
                        sqlSplit = sqlSplit.substring(edTag + 1);
                        continue;
                    }
                }
                {
                    int opTag = sqlSplit.indexOf("[^");
                    if (opTag >= 0) {
                        code += "__sql__+=\"" + sqlSplit.substring(0, opTag) + "\";";
                        sqlSplit = sqlSplit.substring(opTag + 2);
                        int edTag = sqlSplit.indexOf("]");
                        String[] itemCodeAndSeparator = sqlSplit.substring(0, edTag).split(" ");
                        if (itemCodeAndSeparator[1] == null || itemCodeAndSeparator[1].length() == 0) {
                            itemCodeAndSeparator[1] = " ";
                        }
                        code += "\n";
                        code += "flag = false;\n";
                        code += "for(Object __item" + itemIndex + "__ : " + itemCodeAndSeparator[0] + "){\n";
                        code += "__sql__+=\" #{P_\"+__pindex__+\"} \";";
                        code += "__sql__+=\" " + itemCodeAndSeparator[1] + " \";\n";
                        code += "__map__.put(\"P_\" + __pindex__,__item" + itemIndex + "__);\n";
                        code += "__pindex__++;";
                        code += "flag = true;\n";
                        code += "}\n";
                        code += "if(flag)__sql__=__sql__.substring(0,__sql__.length()-" + (itemCodeAndSeparator[1].length() + 2) + ");\n";
                        itemIndex++;
                        sqlSplit = sqlSplit.substring(edTag + 1);
                        continue;
                    }
                }
                code += "__sql__+=\"" + sqlSplit + " \";\n";
                break;
            }
        }
        code += "__map__.put(\"Sql\",__sql__);\n";
        code += "return __map__;\n";
        return code;
    }

    /**
     * 替换XSQL特殊占位符后的文本
     * 
     * @param text 经过Groovy解析后的文本
     * @return 替换特殊占位符后的文本
     */
    private static String processString(String text) {
        int tag = text.indexOf("[?");
        if (tag < 0) {
            return text;
        }
        String[] splits = text.split("\\[\\?");
        text = "";
        for (int i = 1; i < splits.length; i = i + 2) {
            String ltext = splits[i - 1].trim();
            int tag2 = splits[i].indexOf("]");
            String rtext = splits[i].substring(tag2 + 1).trim();
            String ignore = splits[i].substring(0, tag2).trim();
            if (ltext.endsWith(ignore)) {
                ltext = ltext.substring(0, ltext.length() - ignore.length());
            } else if (rtext.startsWith(ignore)) {
                rtext = rtext.substring(ignore.length());
            }
            text += ltext + " " + rtext + " ";
        }
        return text.replaceAll("\\s+", " ");
    }

    @Data
    @AllArgsConstructor
    public static class SQL {
        private String sql;
        private Map<String, Object> params;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static SQL load(String script, Map<String, Object> params) {
        String code = sqlToGroovyCode(script);
        Map<String, Object> r = (Map) executeGroovyScript(code, params);
        String sql = processString((String) r.get("Sql"));
        r.remove("Sql");
        return new SQL(sql, r);
    }

    public static void main(String[] args) {

    }
}

class SqlLoaderDemo {
    private static String xsql = """
            SELECT * FROM `hello`
            WHERE [?AND]
            if (param.getId()!=null) {
                AND `id`=[#param.getId()]
            }
            if (param.getName()!=null){
                AND `name`=[#param.getName()]
            }
            if(param.getTags()!=null&&param.getTags().size()>0){
                AND `name` IN ([^param.getTags() ,])
            }
            [?WHERE]
            """;

    @Data
    private static class Param {
        public static int index = 1;
        private Integer id = index++;
        private String name = "snow";
        private List<String> tags = Arrays.asList("hello", "world");
    }

    public static void main(String[] args) {
        long time1 = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            SqlLoader.SQL sql = SqlLoader.load(xsql, Map.of("param", new Param()));
            System.out.println(sql);
        }
        System.out.println("完成耗时：" + (System.currentTimeMillis() - time1));// 5000
    }

}